package utils;

import java.awt.Color;
import java.util.ArrayList;

import lights.ILight;
import raytracing.ICamera;
import raytracing.Ray;
import raytracing.RaycastResult;
import shading.AmbientShader;
import shading.IShader;
import structs.Vector3;
import worldobjects.IWorldObject;

public class Scene {

	public ArrayList<IWorldObject> worldObjects;
	public ArrayList<ICamera> cameras;
	public ArrayList<IShader> shaders;
	public ArrayList<ILight> lights;
	public Color backgroundColor;
	public boolean shadowsEnabled;
	public boolean reflectionsEnabled;
	public int reflectionsDepth;

	public Scene() {
		worldObjects = new ArrayList<IWorldObject>();
		cameras = new ArrayList<ICamera>();
		shaders = new ArrayList<IShader>();
		lights = new ArrayList<ILight>();
		backgroundColor = new Color(0, 0, 0);
	}

	public void render(RenderPanel panel, ICamera camera) {
		Ray ray = new Ray();

		panel.clear();
		for (int col = 0; col < panel.getWidth(); col++) {
			for (int row = 0; row <= panel.getHeight(); row++) {
				ray = camera.getRay(row, col);

				if (row == 181 && col == 142) {
					System.out.println("Reached row " + row + ", col " + col);
				}

				RaycastResult result = raycastScene(ray, 0, Integer.MAX_VALUE);
				if (result != null) {
					panel.drawPixel(row, col, getShadingColor(result));
				} else
					panel.drawPixel(row, col, backgroundColor);
			}
		}

		panel.redraw();
		panel.saveImage("Data/autosave/autosave.png");
		System.out.println("Done rendering!");
	}

	private RaycastResult raycastScene(Ray ray, float minDist, float maxDist) {
		RaycastResult result = new RaycastResult();

		for (IWorldObject obj : worldObjects) {
			RaycastResult tempRes = new RaycastResult();
			if (obj.isHit(ray, minDist, maxDist, tempRes)) {
				if (tempRes.distance > 0 && tempRes.distance < result.distance)
					result = tempRes;
			}
		}

		if (result.distance == Integer.MAX_VALUE)
			return null;
		return result;
	}

	private Color getShadingColor(RaycastResult res) {
		if (shaders.size() == 0 || lights.size() == 0)
			return res.diffuseColor;

		Vector3 color = new Vector3(0, 0, 0);
		Vector3 hitPoint = res.ray.Origin.plus(res.ray.Direction.multiply(res.distance));

		for (ILight light : lights) {
			Vector3 lightDir = light.getLightDirection(hitPoint);

			boolean isInShadow = false;
			if (shadowsEnabled) {
				Ray shadowRay = new Ray();
				shadowRay.Origin = hitPoint;
				shadowRay.Direction = lightDir;
				isInShadow = hitSceneForShadows(shadowRay, 0.001f, light.getDistanceToLight(hitPoint));
			}

			for (IShader shader : shaders) {

				if (shader instanceof AmbientShader) { // Ambient is always
														// calculated
					Color shade = shader.getShading(res, lightDir, light.getColor());
					color = color.plus(new Vector3(shade.getRed(), shade.getGreen(), shade.getBlue()));
				} else if (!isInShadow) // Only calculate other shaders when not
										// in shadow
				{
					Color shade = shader.getShading(res, lightDir, light.getColor());
					color = color.plus(new Vector3(shade.getRed(), shade.getGreen(), shade.getBlue()));

				}
				
//				if(isInShadow)
//				{
//					color = new Vector3(255,0,0);
//				}
			}
		}

		if (reflectionsEnabled && res.reflective) {
			calculateReflections(res, color);
		}
		
		if(res.selfllumination != 1)
		{
			color = color.multiply(res.selfllumination);
		}

		if (color.X > 255)
			color.X = 255;
		if (color.Y > 255)
			color.Y = 255;
		if (color.Z > 255)
			color.Z = 255;

		return new Color(color.X / 255, color.Y / 255, color.Z / 255);
	}

	private boolean hitSceneForShadows(Ray shadowRay, float minValue, float maxValue) {
		for (IWorldObject obj : worldObjects) {
			if(!obj.castsShadow())
				continue;
			
			if (obj.isHit(shadowRay, minValue, maxValue, new RaycastResult())) {
				return true;
			}
		}
		return false;
	}

	private void calculateReflections(RaycastResult result, Vector3 currentColor) {
		
		if(reflectionsDepth <= 0)
			return;
		
		if (result.specularColor.getRed() == 0 && result.specularColor.getGreen() == 0 && result.specularColor.getBlue() == 0)
			return;

		reflectionsDepth--;
		Vector3 intersectPoint = result.ray.Origin.plus(result.ray.Direction.multiply(result.distance));
		Vector3 newDirection = result.ray.Direction.plus(result.normal.multiply(-2 * result.ray.Direction.dotproduct(result.normal)));
		newDirection.normalize();
		Ray newRay = new Ray();
		newRay.Origin = intersectPoint;
		newRay.Direction = newDirection;
		RaycastResult reflResult = raycastScene(newRay, 0.001f, Integer.MAX_VALUE);
		
		if (reflResult != null) {
			Color reflectionColor = getShadingColor(reflResult);
			currentColor.X += (float)result.specularColor.getRed() / 255 * reflectionColor.getRed();
			currentColor.Y += (float)result.specularColor.getGreen() / 255 * reflectionColor.getGreen();
			currentColor.Z += (float)result.specularColor.getBlue() / 255 * reflectionColor.getBlue();
		}

		reflectionsDepth++;
	}
}
